package hu.debrecen.adastra.card.service.achievement;

import hu.debrecen.adastra.card.repository.AchievementRepository;
import hu.debrecen.adastra.card.repository.PlayerStatisticsRepository;
import hu.debrecen.adastra.card.repository.domain.Account;
import hu.debrecen.adastra.card.repository.domain.Achievement;
import hu.debrecen.adastra.card.repository.domain.PlayerStatistics;
import hu.debrecen.adastra.card.service.AbstractGame;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.Cache.ValueWrapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

public class AchievementService {

	private static final Logger LOGGER = LoggerFactory.getLogger(AchievementService.class);

	private static final Function<AvaiableAchievement, String> TRANSFORM_TO_STRING = new Function<AvaiableAchievement, String>() {
		@Override
		public String apply(final AvaiableAchievement achievement) {
			return achievement.getId();
		}
	};

	private AchievementRepository achievementRepository;

	private PlayerStatisticsRepository playerStatisticsRepository;

	private CacheManager cacheManager;

	private List<AvaiableAchievement> achievements;

	AchievementService() {
		super();
	}

	public AchievementService(final AchievementRepository achievementRepository, final PlayerStatisticsRepository playerStatisticsRepository, final CacheManager cacheManager,
			final List<AvaiableAchievement> achievements) {
		super();
		this.achievementRepository = achievementRepository;
		this.playerStatisticsRepository = playerStatisticsRepository;
		this.cacheManager = cacheManager;
		this.achievements = achievements;
	}

	@SuppressWarnings("unchecked")
	@Transactional(propagation = Propagation.REQUIRED)
	public List<String> getNewAchievements(final Account account) {
		List<Achievement> newAchievements = this.achievementRepository.findNewAchievementsByUsername(account.getUsername());

		if (newAchievements.isEmpty()) {
			return Lists.newArrayList();
		}

		Cache achievementCache = this.cacheManager.getCache("achievements");

		ValueWrapper valueWrapper = achievementCache.get(account.getUsername());
		if (valueWrapper != null) {
			List<String> cachedAchievements = (List<String>) valueWrapper.get();
			AchievementService.LOGGER.trace("Remove achievements(count: {}) from cache by account({}).", cachedAchievements.size(), account.getUsername());
			achievementCache.evict(account.getUsername());
		}

		this.achievementRepository.save(Iterables.transform(newAchievements, new Function<Achievement, Achievement>() {
			@Override
			public Achievement apply(final Achievement achievement) {
				achievement.setShowed(Boolean.TRUE);
				return achievement;
			}
		}));

		return Lists.transform(newAchievements, new Function<Achievement, String>() {
			@Override
			public String apply(final Achievement achievement) {
				return achievement.getAchievementId();
			}
		});
	}

	@Cacheable(value = "achievements", key = "#account.username")
	public List<String> getAchievements(final Account account) {

		List<String> achievements = this.achievementRepository.getAchivedAchievementsByAccount(account.getUsername());
		AchievementService.LOGGER.trace("Put achievements(count: {}) into cache for account({}).", achievements.size(), account.getUsername());

		return achievements;
	}

	public void evaluate(final AbstractGame<?> abstractGame) {

		for (final Account player : abstractGame.getPlayers()) {

			List<String> achievedAchievementIds = this.achievementRepository.getAchivedAchievementsByAccount(player.getUsername());
			PlayerStatistics playerStatistics = this.playerStatisticsRepository.findPlayerStatisticsByAccount(player.getUsername());

			this.achievementRepository.save(Lists.transform(this.evaluate(achievedAchievementIds, playerStatistics), new Function<String, Achievement>() {

				@Override
				public Achievement apply(final String achievementId) {
					AchievementService.LOGGER.debug("Save achievement({})", achievementId);
					return new Achievement(player, achievementId);
				}

			}));

		}

	}

	private List<String> evaluate(final List<String> achievedAchievementIds, final PlayerStatistics playerStatistics) {

		Predicate<AvaiableAchievement> filterNewAchievements = new Predicate<AvaiableAchievement>() {
			@Override
			public boolean apply(final AvaiableAchievement achievement) {
				return !Iterables.contains(achievedAchievementIds, achievement.getId());
			}
		};

		Predicate<AvaiableAchievement> filterAchievedAchievements = new Predicate<AvaiableAchievement>() {
			@Override
			public boolean apply(final AvaiableAchievement achievement) {

				boolean achieved = achievement.achieved(playerStatistics);

				if (achieved) {
					AchievementService.LOGGER.trace("Player({}) achieved '{}' achievement", playerStatistics.getAccount(), achievement.getId());
				}

				return achieved;
			}
		};

		return Lists.newArrayList(Iterables.transform(Iterables.filter(this.achievements, Predicates.and(filterNewAchievements, filterAchievedAchievements)), AchievementService.TRANSFORM_TO_STRING));
	}

	public List<String> getAchievements() {
		return Lists.newArrayList(Iterables.transform(this.achievements, AchievementService.TRANSFORM_TO_STRING));
	}

}
